using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using System.Xml;

namespace Catsland.Core
{
    /// <summary>
    /// Game Engine
    /// </summary>
    public class GameEngine : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager _graphics;
        SpriteBatch _spriteBatch;
        IEditor _editor;   // send messages to editor, if in editor mode
        public IEditor Editor
        {
            get { return _editor; }
        }
		bool delayReleaseScene = false;
        DialogBox _dialogBox;    // just for testing
		public GameEngineMode _gameEngineMode;
        public enum GameEngineMode
        {
            InGame,
            MapEditor
        };
        public InEditorMode _gameInEditorMode;
        public enum InEditorMode
        {
            Editing,
            Playing
        };

        public GameEngine(IEditor editor = null)
        {
            _graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            // set gameEngineMode
            _gameEngineMode = GameEngineMode.InGame;
            if (editor != null)
			{
                // editor mode
				_editor = editor;
				_gameEngineMode = GameEngineMode.MapEditor;
                _gameInEditorMode = InEditorMode.Editing;

                // set handlers for window size change events
				_graphics.PreparingDeviceSettings +=
					new EventHandler<PreparingDeviceSettingsEventArgs>(graphics_PreparingDeviceSettings);
				System.Windows.Forms.Control.FromHandle((this.Window.Handle)).VisibleChanged +=
					new EventHandler(GameEngine_VisibleChanged);
			}

            // Load CatComponent classes and TriggerInvoker classes
            TypeManager typeManager = new TypeManager();
            typeManager.Load_Plugins(@"../../../CatsPlugin/bin/x86/Debug");
            Mgr<TypeManager>.Singleton = typeManager;         
		}

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            base.Initialize();

            // disable depth test. draw sprites from back to front
            DepthStencilState dss = new DepthStencilState();
            dss.DepthBufferEnable = false;
            GraphicsDevice.DepthStencilState = dss;

            // set window size change handler
            // do not allow change window size in release mode
#if DEBUG
            this.Window.AllowUserResizing = true;
#endif
            this.Window.ClientSizeChanged += new EventHandler<EventArgs>(Window_ClientSizeChanged);
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            _spriteBatch = new SpriteBatch(GraphicsDevice);

            // Init singleton
            Mgr<GameEngine>.Singleton = this;
            Mgr<GraphicsDevice>.Singleton = GraphicsDevice;
            Mgr<BasicEffect>.Singleton = new BasicEffect(GraphicsDevice);
            Mgr<BasicEffect>.Singleton.Name = "BasicEffect";
            Mgr<BasicEffect>.Singleton.GraphicsDevice.BlendState = BlendState.AlphaBlend;
            Mgr<DebugTools>.Singleton = new DebugTools();
            Mgr<DebugTools>.Singleton.DrawEffect = new BasicEffect(GraphicsDevice);

            // Camera
            // TODO: camera should be loaded from map file
            #region
            Camera camera = new Camera();

            float viewWidth = 2.0f;
            float viewHeight = Window.ClientBounds.Height * viewWidth / Window.ClientBounds.Width;
            camera.setOrthographic(viewWidth, viewHeight, 0.1f, 100.0f);
            camera.m_view = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 2.0f),
                new Vector3(0.0f, 0.0f, 0.0f),
                new Vector3(0.0f, 1.0f, 0.0f));
            Mgr<Camera>.Singleton = camera;
            #endregion
            // UI
            // TODO: UI should be loaded from map file
            #region
            _dialogBox = new DialogBox();
            Mgr<DialogBox>.Singleton = _dialogBox;
            _dialogBox.m_enable = false;
            _dialogBox.m_leftTopTex = Content.Load<Texture2D>(@"image/lefttop");
            _dialogBox.m_topTex = Content.Load<Texture2D>(@"image/top");
            _dialogBox.m_insideTex = Content.Load<Texture2D>(@"image/inside");
            _dialogBox.m_leftTex = Content.Load<Texture2D>(@"image/left");
            _dialogBox.m_font = Content.Load<SpriteFont>(@"font/YaheiFont");
            _dialogBox.m_leftTop = new Point(10, 10);
            _dialogBox.m_rightBottom = new Point(400, 200);
            #endregion

            if (_gameEngineMode == GameEngineMode.InGame)
            {
                // TODO: the first level in game should be decided by bootstrap file
                XmlDocument doc = new XmlDocument();
                doc.Load("../../../../../test/catsmap.xml");

                XmlNode scene = doc.SelectSingleNode("Scene");
                // create new scene a load
                Scene newScene = Scene.LoadFromNode(scene, this);
                newScene.InitializeScene();
                newScene.ActivateScene();
                Mgr<Scene>.Singleton = newScene;
            }
            else
            {
                // editor mode, create an empty scene
                Mgr<Scene>.Singleton = Scene.EmptyScene(this);
                Mgr<Scene>.Singleton.InitializeScene();
                Mgr<Scene>.Singleton.ActivateScene();
            }

        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // this is the right point to release running scene
            if (delayReleaseScene)
            {
                Mgr<Scene>.Singleton.Unload();
                delayReleaseScene = false;
            }

            

            GameObjectList gameObjectList = null;
            ColliderList colliderList = null;
            if (Mgr<Scene>.Singleton != null)
            {
                gameObjectList = Mgr<Scene>.Singleton._gameObjectList;
                colliderList = Mgr<Scene>.Singleton._colliderList;
            }
            
            // add / remove update gameObject
            if (gameObjectList != null)
            {
                gameObjectList.UpdateAdd();
                gameObjectList.UpdateRemove();
            }

            if (colliderList != null)
            {
                colliderList.UpdateRemove();
            }
            if (_gameEngineMode == GameEngineMode.InGame || 
                _gameInEditorMode == InEditorMode.Playing)
            {
                // Allows the game to exit
                if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                    this.Exit();

                // camera
                Mgr<Camera>.Singleton.Update();

                // gameObjects
                if (gameObjectList != null)
                {
                    gameObjectList.Update(gameTime.ElapsedGameTime.Milliseconds);
                }
                
            }
            else if (_gameInEditorMode == InEditorMode.Editing)
            {
                // camera
                Mgr<Camera>.Singleton.EditorUpdate();

                if (gameObjectList != null)
                {
                    gameObjectList.EditorUpdate(gameTime.ElapsedGameTime.Milliseconds);
                }
            }
            
            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Black);

            // draw render list
            RenderList renderList = null;
            DebugDrawableList debugDrawableList = null;
            if (Mgr<Scene>.Singleton != null)
            {
                renderList = Mgr<Scene>.Singleton._renderList;
                debugDrawableList = Mgr<Scene>.Singleton._debugDrawableList;
            }
            if (renderList != null)
            {
                renderList.Draw(gameTime.ElapsedGameTime.Milliseconds);
            }

            // draw debug info
            if (debugDrawableList != null)
            {
                debugDrawableList.Draw(gameTime.ElapsedGameTime.Milliseconds);
            }

            // draw UI
            _spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearWrap,
    DepthStencilState.None, RasterizerState.CullNone);
            _dialogBox.Draw(_spriteBatch);
            _spriteBatch.End();

            base.Draw(gameTime);
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        // editor window prepare handler
		void graphics_PreparingDeviceSettings(object sender, PreparingDeviceSettingsEventArgs e)
		{
            // get render size from editor
			e.GraphicsDeviceInformation.PresentationParameters.DeviceWindowHandle = _editor.GetRenderAreaHandle();
			Point size = _editor.GetRenderAreaSize();
            // update render device
			e.GraphicsDeviceInformation.PresentationParameters.BackBufferWidth = size.X;
			e.GraphicsDeviceInformation.PresentationParameters.BackBufferHeight = size.Y;
		}
        
        // window size change handler (for editor mode)
		public void GameEngine_SizeChanged()
		{
            if (_editor != null)
            {
                // get render size from editor
                Point size = _editor.GetRenderAreaSize();
                _graphics.PreferredBackBufferWidth = size.X;
                _graphics.PreferredBackBufferWidth = size.Y;
                // size of view coordinates
                float viewWidth = 2.0f;
                float viewHeight = size.Y * viewWidth / size.X;
                // set camera matrix
                if (Mgr<Camera>.Singleton != null)
                {
                    Mgr<Camera>.Singleton.setOrthographic(viewWidth, viewHeight, 0.1f, 100.0f);
                }
            }
            else
            {
                // TODO: replace with error log system
                Console.Out.WriteLine("No editor found.");
            }
		}
        
        // render window visible change handler (for editor mode)
		private void GameEngine_VisibleChanged(object sender, EventArgs e)
		{
			if (System.Windows.Forms.Control.FromHandle((this.Window.Handle)).Visible == true)
			{
				System.Windows.Forms.Control.FromHandle((this.Window.Handle)).Visible = false;
			}
		}

        // render window visible change handler (for game mode)
		void Window_ClientSizeChanged(object sender, EventArgs e)
		{
			float viewWidth = 2.0f;
			float viewHeight = Window.ClientBounds.Height * viewWidth / Window.ClientBounds.Width;
			Mgr<Camera>.Singleton.setOrthographic(viewWidth, viewHeight, 0.1f, 100.0f);
			_graphics.PreferredBackBufferWidth = Window.ClientBounds.Width;
			_graphics.PreferredBackBufferHeight = Window.ClientBounds.Height;
		}

		public void DelayReleaseScene()
		{
			delayReleaseScene = true;
		}
    }
}
